
package parser.lr;

import java.util.Iterator;
import java.util.Stack;
import java.util.List;
import java.util.LinkedList;
import java.util.ArrayList;
import java.util.Iterator;

import java.lang.IllegalStateException;

import lexer.Token;
import parser.Production;
import parser.Parser;
import parser.SyntaxTranslator;

import parser.exception.ParsingException;

/**
 * Parser LR.
 */
public class LRParser implements Parser {
	
	private final LRAction[][] table;
	private final Production[] productions;
	private final Integer initialState;
	private final String nonterminals;
	private final int numTerminals;
	
	private transient Iterator<Token> input;
	private transient Token next;
	
	public LRParser(String nonterminals, int numTerminals, Production[] productions,
			LRAction[][] table, int initialState) {
		this.table = table;
		this.productions = productions;
		this.initialState = initialState;
		this.nonterminals = nonterminals;
		this.numTerminals = numTerminals;
	}
	
	
	public <T> T parse(Iterator<Token> input, SyntaxTranslator<T> output) throws ParsingException {
		this.input = input;
		this.next = getNext();
		
		Stack<Integer> states = new Stack<Integer>();
		states.push(initialState);
		
		Stack<T> trees = new Stack<T>();
		
		loop:
		while(true) {
			LRAction action = table[states.peek() - 1][next == null ? 0 : next.getId()];
			
			if(action == null) {
				throw new ParsingException();
			}
				
			switch(action.getType()) {
				case ACCEPT:
					break loop;
				case SHIFT:
					//System.out.println("s" + action.getValue());
					
					states.push(action.getValue());
					trees.push(output.translateTerminal(next));
					
					next = getNext();
					break;
				case REDUCE:
					//System.out.println("r" + action.getValue());
					
					Production p = productions[action.getValue() - 1];
					int len = p.getBody().length();
					
					LinkedList<T> children = new LinkedList<T>();
					
					for(int i = len - 1; i >= 0; i--) {
						children.push(trees.pop());
						states.pop();
					}
					
					trees.push(output.translateNonterminal(p, children));
					states.push(table[states.peek() - 1][getNonterminalIndex(p.getHead())].getValue());
			}
		}
		
		this.input = null;
		
		if(trees.size() != 1)
			throw new IllegalStateException(trees.size() + " resulting trees");
		
		return trees.pop();
	}
	
	private Token getNext() {
		return input.hasNext() ? input.next() : null;
	}
	
	private int getNonterminalIndex(Character c) {
		return nonterminals.indexOf(c.toString()) + numTerminals + 1;
	}
}
